// 《围棋》作者版权所有。保留所有权利。
// 此源代码的使用受BSD样式
// 许可证的约束，该许可证可以在许可证文件中找到。

package rsa

// 此文件根据RFC 8017实现了RSASSA-PSS签名方案。

import (
	"bytes"
	"crypto"
	"errors"
	"hash"
	"io"
	"math/big"
)

// 根据RFC 8017，9.1 
// ：：：
// 

func emsaPSSEncode(mHash []byte, emBits int, salt []byte, hash hash.Hash) ([]byte, error) {
	// 参见RFC 8017第9.1.1节。

	hLen := hash.Size()
	sLen := len(salt)
	emLen := (emBits + 7) / 8

	// 1。如果M的长度大于
	// 哈希函数的输入限制（SHA-1为2^61-1个八位字节），则输出“消息太
	// 长”，并停止。
	// 
	// 2。设mHash=Hash（M），长度为hLen的八位字节字符串。

	if len(mHash) != hLen {
		return nil, errors.New("crypto/rsa: input must be hashed with given hash")
	}

	// 3。如果emLen<hLen+sLen+2，则输出“编码错误”并停止。

	if emLen < hLen+sLen+2 {
		return nil, errors.New("crypto/rsa: key size too small for PSS signature")
	}

	em := make([]byte, emLen)
	psLen := emLen - sLen - hLen - 2
	db := em[:psLen+1+sLen]
	h := em[psLen+1+sLen : emLen-1]

	// 4。生成长度为sLen的随机八位组字符串；如果sLen=0，
	// 则salt为空字符串。
	// 
	// 5。让
	// M'=（0x）00 00 | | mHash | |盐；
	// 
	// M'是一个长度为8+hLen+sLen的八位字节字符串，包含八个
	// 初始值为零的八位字节。
	// 
	// 6。设H=Hash（M'），长度为hLen的八位字节字符串。

	var prefix [8]byte

	hash.Write(prefix[:])
	hash.Write(mHash)
	hash.Write(salt)

	h = hash.Sum(h[:0])
	hash.Reset()

	// 7。生成由emLen-sLen-hLen-2 
	// 零个八位字节组成的八位字节字符串PS。PS的长度可以是0。
	// 
	// 8。设DB=PS | | 0x01 | | salt；DB是长度为
	// emLen-hLen-1的八位字节字符串。

	db[psLen] = 0x01
	copy(db[psLen+1:], salt)

	// 9。设dbMask=MGF（H，emLen-hLen-1）。
	// 
	// 10。让maskedDB=DB\xor dbMask。

	mgf1XOR(db, hash, h)

	// 11。将
	// maskedDB中最左边八位字节的最左边8*emLen-emBits设置为零。

	db[0] &= 0xff >> (8*emLen - emBits)

	// 12。设EM=maskedDB | | H | | 0xbc。
	em[emLen-1] = 0xbc

	// 13。输出EM.
	return em, nil
}

func emsaPSSVerify(mHash, em []byte, emBits, sLen int, hash hash.Hash) error {
	// 参见RFC 8017第9.1.2节。

	hLen := hash.Size()
	if sLen == PSSSaltLengthEqualsHash {
		sLen = hLen
	}
	emLen := (emBits + 7) / 8
	if emLen != len(em) {
		return errors.New("rsa: internal error: inconsistent length")
	}

	// 1。如果M的长度大于
	// 哈希函数的输入限制（SHA-1为2^61-1个八位字节），则输出“不一致”
	// 并停止。
	// 
	// 2。设mHash=Hash（M），长度为hLen的八位字节字符串。
	if hLen != len(mHash) {
		return ErrVerification
	}

	// 3。如果emLen<hLen+sLen+2，则输出“不一致”并停止。
	if emLen < hLen+sLen+2 {
		return ErrVerification
	}

	// 4。如果EM最右边的八位字节没有十六进制值
	// 0xbc，则输出“不一致”并停止。
	if em[emLen-1] != 0xbc {
		return ErrVerification
	}

	// 5。设maskedDB为EM最左边的emLen-hLen-1八位组，
	// 设H为下一个hLen八位组。
	db := em[:emLen-hLen-1]
	h := em[emLen-hLen-1 : emLen-1]

	// 6。如果
	// maskedDB中最左边八位字节的最左边8*emLen-emBits不都等于零，则输出“不一致”，
	// stop。
	var bitMask byte = 0xff >> (8*emLen - emBits)
	if em[0] & ^bitMask != 0 {
		return ErrVerification
	}

	// 7。设dbMask=MGF（H，emLen-hLen-1）。
	// 
	// 8。设DB=maskedDB\xor dbMask。
	mgf1XOR(db, hash, h)

	// 9。将DB 
	// 中最左边八位字节的最左边8*emLen-emBits设置为零。
	db[0] &= bitMask

	// 如果我们不知道盐的长度，请查找0x01分隔符。
	if sLen == PSSSaltLengthAuto {
		psLen := bytes.IndexByte(db, 0x01)
		if psLen < 0 {
			return ErrVerification
		}
		sLen = len(db) - psLen - 1
	}

	// 10。如果DB的emLen-hLen-sLen-2最左边的八位字节不是零
	// 或者如果emLen-hLen-sLen-1位置的八位字节（最左边的
	// 位置是“位置1”）没有十六进制值0x01，
	// 输出“不一致”并停止。
	psLen := emLen - hLen - sLen - 2
	for _, e := range db[:psLen] {
		if e != 0x00 {
			return ErrVerification
		}
	}
	if db[psLen] != 0x01 {
		return ErrVerification
	}

	// 11。让盐成为DB的最后一个sLen八位组。
	salt := db[len(db)-sLen:]

	// 12。设
	// M'=（0x）00 00 | | mHash | |盐；
	// M'是一个长度为8+hLen+sLen的八位字节字符串，包含八个
	// 初始值为零的八位字节。
	// 
	// 13。设H'=Hash（M'），长度为hLen的八位字节字符串。
	var prefix [8]byte
	hash.Write(prefix[:])
	hash.Write(mHash)
	hash.Write(salt)

	h0 := hash.Sum(nil)

	// 14。如果H=H’，输出“一致”否则，输出“不一致”
	if !bytes.Equal(h0, h) { // TODO:恒定时间？
		return ErrVerification
	}
	return nil
}

// signPSSWithSalt使用带指定盐的PSS计算哈希的签名。
// 注意，hashed必须是使用给定的hash函数对输入消息进行哈希运算的结果。salt是一个随机字节序列，其长度为
// 稍后用于验证签名。
func signPSSWithSalt(rand io.Reader, priv *PrivateKey, hash crypto.Hash, hashed, salt []byte) ([]byte, error) {
	emBits := priv.N.BitLen() - 1
	em, err := emsaPSSEncode(hashed, emBits, salt, hash.New())
	if err != nil {
		return nil, err
	}
	m := new(big.Int).SetBytes(em)
	c, err := decryptAndCheck(rand, priv, m)
	if err != nil {
		return nil, err
	}
	s := make([]byte, priv.Size())
	return c.FillBytes(s), nil
}

const (
	// PSSSaltLengthAuto使PSS签名中的盐与
	// 签名时尽可能多，验证时自动检测。
	PSSSaltLengthAuto = 0
	// PSSALT长度QUALSHASH使salt长度等于签名中使用的哈希的长度
	// 。
	PSSSaltLengthEqualsHash = -1
)

// PSS选项包含用于创建和验证PSS签名的选项。
type PSSOptions struct {
	// SaltLength控制PSS中使用的盐的长度
	// 签名。它可以是一个字节数，也可以是一个特殊的
	// PSSSaltLength常量。
	SaltLength int

	// Hash是用于生成消息摘要的哈希函数。如果不是
	// zero，它将覆盖传递给SignPSS的哈希函数。使用私钥时需要
	// 。签名
	Hash crypto.Hash
}

// HashFunc返回选项。散列，以便PSS选项实现加密。先生。
func (opts *PSSOptions) HashFunc() crypto.Hash {
	return opts.Hash
}

func (opts *PSSOptions) saltLength() int {
	if opts == nil {
		return PSSSaltLengthAuto
	}
	return opts.SaltLength
}

// SignPSS使用PSS计算摘要的签名。
// 
// 摘要必须是使用给定哈希
// 函数对输入消息进行哈希运算的结果。opts参数可能为零，在这种情况下，可以使用
// 合理的默认值。如果选择的话。如果设置了哈希，它将覆盖哈希。
func SignPSS(rand io.Reader, priv *PrivateKey, hash crypto.Hash, digest []byte, opts *PSSOptions) ([]byte, error) {
	if opts != nil && opts.Hash != 0 {
		hash = opts.Hash
	}

	saltLength := opts.saltLength()
	switch saltLength {
	case PSSSaltLengthAuto:
		saltLength = (priv.N.BitLen()-1+7)/8 - 2 - hash.Size()
	case PSSSaltLengthEqualsHash:
		saltLength = hash.Size()
	}

	salt := make([]byte, saltLength)
	if _, err := io.ReadFull(rand, salt); err != nil {
		return nil, err
	}
	return signPSSWithSalt(rand, priv, hash, digest, salt)
}

// VerifyPSS验证PSS签名。
// 
// 返回零错误表示签名有效。摘要必须是
// 使用给定哈希函数对输入消息进行哈希运算的结果。opts 
// 参数可能为零，在这种情况下，使用合理的默认值。选择。Hash是
// 忽略。
func VerifyPSS(pub *PublicKey, hash crypto.Hash, digest []byte, sig []byte, opts *PSSOptions) error {
	if len(sig) != pub.Size() {
		return ErrVerification
	}
	s := new(big.Int).SetBytes(sig)
	m := encrypt(new(big.Int), pub, s)
	emBits := pub.N.BitLen() - 1
	emLen := (emBits + 7) / 8
	if m.BitLen() > emLen*8 {
		return ErrVerification
	}
	em := m.FillBytes(make([]byte, emLen))
	return emsaPSSVerify(digest, em, emBits, opts.saltLength(), hash.New())
}
